package luj.tool.jnproxyan.proxy.util.data.load.internal;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import luj.data.api.Data;
import luj.data.api.DataContext;
import luj.data.api.field.TypeField;
import luj.data.api.field.TypeInField;
import luj.data.api.type.DataType;
import luj.tool.jnproxyan.proxy.database.DatabaseState;
import luj.tool.jnproxyan.proxy.util.data.DataDirtySaver;
import org.jooq.DSLContext;
import org.jooq.Field;
import org.jooq.Record;
import org.jooq.Table;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;

@Component
public class DataInLoadMaker {

  public DataInLoadMaker inject(Long dataId, String valueStr, DatabaseState dbState, Class<?> type) {
    DataInLoadMaker result = new DataInLoadMaker();
    result._dataId = dataId;
    result._valueStr = valueStr;
    result._dbState = dbState;
    result._type = type;

    result._lujdata = _lujdata;
    result._jsonFixer = _jsonFixer;

    return result;
  }

  public Data make() {
    Data data = makeData(_dataId);
    Map<String, Object> valueMap = parseJson(_valueStr);
    for (Map.Entry<String, Object> e : valueMap.entrySet()) {
      data.setField(e.getKey(), e.getValue());
    }

    DataType type = data.getType();
    type.getFields().stream()
        .filter(f -> f.getType().isMap())
        .forEach(f -> data.setField(f.getName(), readMap(f, data)));

    return data;
  }

  private Data makeData(Long id) {
    Data data = _lujdata.createData(id);
    data.setType(_dbState.getDataTypes().getType(_type));
    return data;
  }

  private Map<Object, Object> readMap(TypeField field, Data parent) {
    Map<Object, Object> resultMap = new LinkedHashMap<>();

    DSLContext jooq = _dbState.getJooq();
    String typeName = parent.getType().getName();
    String table = typeName + "#" + field.getName();

    List<String> dataList = jooq.select(F_DATA).from(T_DATAN)
        .where(F_TABLE.eq(table)).and(F_PARENT.eq((Long) parent.getId()))
        .fetch(F_DATA);

    TypeInField.Map type = field.getType().asMapType();
    Class<?> keyClass = type.getKeyType().asJavaClass();
    Class<?> valueClass = type.getValueType().asJavaClass();

    for (String dataStr : dataList) {
      Map<String, Object> entryJson = parseJson(dataStr);
      Object key = fixJsonValue(entryJson.get("key"), keyClass);
      Object value = fixJsonValue(entryJson.get("value"), valueClass);

      resultMap.put(key, value);
    }

    return resultMap;
  }

  private Object fixJsonValue(Object rawVal, Class<?> typeInCode) {
    return _jsonFixer.fix(rawVal, typeInCode);
  }

  @SuppressWarnings("unchecked")
  private Map<String, Object> parseJson(String jsonStr) {
    try {
      return JACKSON.readValue(jsonStr, Map.class);

    } catch (JsonProcessingException e) {
      throw new UnsupportedOperationException(e);
    }
  }

  private static final ObjectMapper JACKSON = new ObjectMapper();

  private static final Table<Record> T_DATA = DataDirtySaver.T_DATA;
  private static final Table<Record> T_DATAN = DataDirtySaver.T_DATAN;

  private static final Field<Long> F_ID = DataDirtySaver.F_ID;
  private static final Field<String> F_TABLE = DataDirtySaver.F_TABLE;
  private static final Field<String> F_DATA = DataDirtySaver.F_DATA;
  private static final Field<Long> F_PARENT = DataDirtySaver.F_PARENT;

  Long _dataId;
  String _valueStr;

  DatabaseState _dbState;
  Class<?> _type;

  @Autowired
  DataContext _lujdata;

  @Autowired
  JsonValueFixer _jsonFixer;
}
